package com.ease.context.aop;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ease.common.util.AopUtil;

/**
 * <a href="http://www.javaeye.com/topic/269763" />
 * 
 * @author Norther
 */
public abstract class TargetClassMethodInterceptor extends AopUtil implements MethodInterceptor {

	private static final transient Log log = LogFactory.getLog(TargetClassMethodInterceptor.class);

	// private List<Class<?>> excludeParameterTypes;
	private Class<?> excludeParameterType;

	/**
	 * get first proxied interface operation: "interfaceName.methodName"
	 */
	protected String getFirstProxiedInterfaceOperation(MethodInvocation invocation) {
		Class<?> targetClass = getTargetClass(invocation.getThis());
		Class<?>[] proxiedInterfaces = targetClass.getInterfaces();

		if (null != proxiedInterfaces && proxiedInterfaces.length > 0) {
			targetClass = proxiedInterfaces[0];
		}
		return targetClass.getName() + "." + getTargetMethod(invocation).getName();
	}

	protected Method getTargetMethod2(MethodInvocation invocation) {
		return invocation.getMethod();
	}

	protected Method getTargetMethod(MethodInvocation invocation) {
		String methodName = invocation.getMethod().getName();
		Class<?>[] parameterTypes = invocation.getMethod().getParameterTypes();
		Object thisObject = invocation.getThis();
		Method[] methods = thisObject.getClass().getMethods();
		// Method method = invocation.getThis().getClass().getMethod(methodName,
		// parameterTypes);
		StringBuilder parameterTypeString = new StringBuilder();
		StringBuilder tempParameterTypeString = new StringBuilder();
		for (Method tempMethod : methods) {
			if (tempMethod.getName().equals(methodName)) {
				parameterTypeString = new StringBuilder();
				tempParameterTypeString = new StringBuilder();
				Class<?>[] tempParameterTypes = tempMethod.getParameterTypes();
				if (tempParameterTypes.length != parameterTypes.length) {
					continue;
				}
				boolean parameterTypesMatch = true;

				for (int i = 0; i < parameterTypes.length; i++) {
					parameterTypeString.append((i == 0) ? "" : ", " + parameterTypes[i]);
					tempParameterTypeString.append((i == 0) ? "" : ", " + tempParameterTypes[i]);
					// if (!tempParameterTypes[i].equals(parameterTypes[i])) {
					if (!parameterTypes[i].isAssignableFrom(tempParameterTypes[i])) {
						parameterTypesMatch = false;
					}
				}
				if (true == parameterTypesMatch) {
					return tempMethod;
				}
			}
		}
		String className = getTargetClass(thisObject).getName();
		log.error("targetMethod not found, " + className + "." + methodName + "(" + parameterTypeString + "). temp is " + tempParameterTypeString);
		return invocation.getMethod();
	}

	/**
	 * get method signature: "className.methodName(parameterType=argument, parameterType=argument)"
	 */
	public String getMethodSignature(MethodInvocation invocation) {
		Class<?> targetClass = getTargetClass(invocation.getThis());
		String className = targetClass.getName();
		String methodName = getTargetMethod(invocation).getName();
		Class<?>[] parameterTypes = invocation.getMethod().getParameterTypes();
		Object[] arguments = invocation.getArguments();

		StringBuilder sb = new StringBuilder();
		sb.append(className).append(".").append(methodName).append("(");
		if ((null != arguments) && (arguments.length != 0)) {
			for (int i = 0; i < arguments.length; i++) {

				if (i > 0) {
					sb.append(", ");
				}
				sb.append(parameterTypes[i].getName()).append("=");

				if (isExcludeParameterTypes(parameterTypes[i])) {
					sb.append(parameterTypes[i]);// excludeParameterType
				} else {
					sb.append(arguments[i]);
				}

			}
		}
		sb.append(")");
		return sb.toString();
	}

	public Map<String, Object> getArguments(MethodInvocation invocation) {
		// Class<?> targetClass = getTargetClass(invocation.getThis());
		// String className = targetClass.getName();
		// String methodName = getTargetMethod(invocation).getName();
		Class<?>[] parameterTypes = invocation.getMethod().getParameterTypes();
		Object[] arguments = invocation.getArguments();

		Map<String, Object> argumentMap = new HashMap<String, Object>();
		if ((null != arguments) && (arguments.length != 0)) {
			for (int i = 0; i < arguments.length; i++) {
				argumentMap.put(parameterTypes[i].getSimpleName(), arguments[i]);
			}
		}

		return argumentMap;
	}

	private boolean isExcludeParameterTypes(Class<?> parameterType) {
		// if (notEmpty(excludeParameterTypes)) {
		// for (Class<?> excludeParameterType : excludeParameterTypes) {
		// if (parameterType.isAssignableFrom(excludeParameterType)) {
		// return true;
		// }
		// }
		// }
		if (null != excludeParameterType) {
			if (parameterType.isAssignableFrom(excludeParameterType)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @return the excludeParameterType
	 */
	public Class<?> getExcludeParameterType() {
		return excludeParameterType;
	}

	/**
	 * @param excludeParameterType
	 *            the excludeParameterType to set
	 */
	public void setExcludeParameterType(Class<?> excludeParameterType) {
		this.excludeParameterType = excludeParameterType;
	}

	//
	// /**
	// * @return the excludeParameterTypes
	// */
	// public List<Class<?>> getExcludeParameterTypes() {
	// return excludeParameterTypes;
	// }
	//
	// /**
	// * @param excludeParameterTypes
	// * the excludeParameterTypes to set
	// */
	// public void setExcludeParameterTypes(List<Class<?>>
	// excludeParameterTypes) {
	// this.excludeParameterTypes = excludeParameterTypes;
	// }
	public static void main(String[] args) {
		// System.out.println(org.springframework.aop.framework.Advised.class.
		// getSimpleName());
		// Pagination<Model> pagination = new Pagination<Model> ();
		// Class<?> clazz= pagination.getClass();
		// System.out.println(clazz);
		// System.out.println(Pagination.class.isAssignableFrom(new
		// Pagination<Model>().getClass()));
	}

}